package org.jugile.daims;

/*

Copyright (C) 2011-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/


import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.jugile.util.DBConnection;
import org.jugile.util.DBPool;
import org.jugile.util.IDOComparator;
import org.jugile.util.Proxy;
import org.jugile.util.Query;

/**
 * <i>"this is verse"</i> <b>()</b>
 * 
 * <br/>==========<br/>
 *
 * here is doc
 *  
 * @author jukka.rahkonen@iki.fi
 *
 */
public abstract class BoCollection<E extends Bo> extends BoMapDelta<E> implements Iterable<E> {
	static Logger log = Logger.getLogger(BoCollection.class);

	public BoCollection(Class<E> cl) { super(cl); }
	protected void setOrigin(BoCollection<E> bc) { origin = bc.origin; }
	
	private boolean readonly = false;
	//protected void setReadOnly() { readonly = true; }
	
	// ----------- old collection services ----------------
	
	private Iterator<E> iterator = null;
	public final Iterator<E> iterator() {
		iterator = new BoCollectionIterator(this);
		return iterator;
	}
	
	private Iterator<E> mapIterator() {
		if (readonly) return origin.iterator();
		return super.deltaIterator();
	}

	public void add(BoCollection<E> bc) { for (E o : bc) add(o); }
	
	public int size() {
		if (q == null) return getSize();
		int s = 0;
		for (E o : this) s++; // must iterate through if query != null
		return s;
	}
	
	// for testing
	public void iterate() {
		for (E o : this) {
			print(" item: " + o);
		}
	}
	
	/**
	 * Query iterator. 
	 */
	private class BoCollectionIterator implements Iterator<E> {
		Iterator<E> i = null;
		BoCollection<E> bc = null;
		private E next = null;
		List<E> items = null; // query items in collection
		
		public BoCollectionIterator(BoCollection<E> bc) { 
			this.bc = bc;
			if (bc.q != null) {
				Iterator<E> i2 = bc.mapIterator();
				items = new ArrayList<E>();
				while (i2.hasNext()) {
					items.add(i2.next());
				}
				i = items.iterator();
			} else {
				i = bc.mapIterator();
			}
			next = getNext();
		}
		
		private E getNext() {
			while (i.hasNext()) {
				E o = i.next();
				if (bc.q != null) {
					if (bc.q.eval(o)) return o;
				} else {
					return o;
				}
			}
			return null;
		}
		
		public E next() {
			E res = next;
			next = getNext();
			return res;
		}
		public boolean hasNext() { return next != null; }
		public void remove() { fail("remove not supported");}		
		
	}

	
	public final E next() {
		if (iterator == null) iterator = iterator(); // if used directly outside of loop
		return iterator.next();
	}
	public final boolean hasNext() { 
		if (iterator == null) iterator = iterator(); // if used directly outside of loop
		return iterator.hasNext();
	}
	
	public final List<E> list() {
		List<E> res = new ArrayList<E>();
		for (E o : this) res.add(o);
		return res;
	}
	
	private Query q = null;
	public Query q() {
		if (q == null) q = new Query();
		return q;
	}
	public void reset() { 
		q = null;
		iterator = null;
	}
	
	public boolean contains(E o) {
		if (o == null) return false;
		if (readonly) return origin.contains(o);
		return (get(o.id()) != null);
	}

	public BoCollection<E> q(String op, String fld, Object value) {
		q().and(op, fld, value); return this;
	}
	public BoCollection<E> q(String op, Method filterMethod, Object[] filterParams) {
		q().filter(op, filterMethod, filterParams); return this;
	}
	
	public List<E> page(int page, int size, String sortcrit) {
		return (List<E>)page((List)sort(sortcrit), page, size);
	}
	private boolean hasPrevPage = false;
	private boolean hasNextPage = false;
	public boolean hasPrevPage() { return hasPrevPage; }
	public boolean hasNextPage() { return hasNextPage; }
	
	private int pageNum = 0;
	public int getPageNum() { return pageNum; }
	
	
	public List<E> sort(String crit) {
		List<E> res = list();
		Collections.sort(res, new IDOComparator(crit));
		return res;
	}

	public List<E> first(int count) { return first(count,"id");} 
	public List<E> first(int count, String sort) {
		List<E> res = sort(sort);
		if (count > res.size()) count = res.size();
		return res.subList(0, count);
	}
	public E first(String sort) { 
		List<E> res = first(1,sort);
		if (res.size() > 0) return res.get(0);
		return null;
	}
	
	public E unique() { 
		Query q2 = q;
		if (size() > 1) {
			String querystr = "";
			try {
				if (q2 != null) querystr = q2.toString();
			} catch (Exception e) {}
			//Jugile.fail("Not unique $on : " + size() + " q: "+querystr);
			log.error("Not unique $on : " + size() + " q: "+querystr);
			return first("id");
		}
		iterator = null;
		if(!hasNext()) return null; 
		return next(); 
	}
	
	

	/**
	 * Paging starts from 0.
	 */
	public List<E> page(List<E> list, int page, int size) {
		int start = page*size;
		int end = start + size;
		if (start >= list.size()) start = list.size();
		if (start < 0) start = 0;
		if (end >= list.size()) end = list.size();
		if (end < 0) end = 0;
		if (start > 0 && (page-1)* size <= list.size()) hasPrevPage = true;
		else hasPrevPage = false;
		if (end < list.size()) hasNextPage = true;
		else hasNextPage = false;
		pageNum = page;
		return list.subList(start, end);
	}

	public List<E> sublist(int start, int end, String sortcrit) {
		return sublist((List)sort(sortcrit), start, end);
	}

	public List<E> sublist(List<E> list, int start, int end) {
		// sanity checks
		int size = list.size();
		if (end > size) end = size;
		if (start > size) start = size;
		if (end < 0) end = 0;
		if (start < 0) start = 0;
		if (end < start) end = start;
		return list.subList(start, end);
	}


	// ---- logical operations -----
	
	public BoCollection<E> and(BoCollection<E> res, BoCollection<E> m) {
		for (E o : this) { if (m.contains(o)) res.add(o); }
		//res.origin = origin;  // TODO: check how it works when objs are modified
		return res;
	}
	
	public BoCollection<E> or(BoCollection<E> res, BoCollection<E> m) {
		for (E o : this) res.add(o);
		for (E o : m) res.add(o);
		return res;
	}

	public BoCollection<E> xor(BoCollection<E> res, BoCollection<E> m) {
		for (E o : m) if (!contains(o)) res.add(o);
		for (E o : this) if (!m.contains(o)) res.add(o);
		return res;
	}
	
	public BoCollection<E> not(BoCollection<E> res, BoCollection<E> m) {
		for (E o : this) if (!m.contains(o)) res.add(o);
		return res;
	}
	
	public BoCollection<E> keep(BoCollection<E> res, String mname) {
		for (E o : this) if (isTrue(o,mname)) res.add(o);
		return res;
	}
	
	public BoCollection<E> not(BoCollection<E> res, String mname) {
		for (E o : this) if (!isTrue(o,mname)) res.add(o);
		return res;
	}
	
	private boolean isTrue(Object item, String mname) {
		Proxy p = new Proxy(item);
		return isTrue(p.call(mname));
	}
	private boolean isTrue(Object res) {
		if (res instanceof Boolean) {
			if ((Boolean)res) return true;
			return false;
		}
		return res != null;
	}
	
	
	// ----- sql interface -----
	
	public List<E> fromDb() { return fromDb(null); }
	public List<E> fromDb(String sql) {
		Query q2 = q();
		Bo bo = getProto();
		if (sql == null) sql = "where " + q2.toSql();
		sql = "select " + bo._getSelectFlds() +" from " + bo.table() + " " + sql;

		List<E> res = new ArrayList<E>();
		DBPool db = DBPool.getPool();
		DBConnection c = db.getConnection();
		try {
			c.prepare(sql);
			for (List<Object> row : c.select()) {
				long id = (Integer)row.get(0);
				E o = (E)bo.createOld(bo.getClass(), id);
				o._setState(null,bo.bi(),row);
				res.add(o);
			}
		} catch (Exception e) {
			log.error("dbread failed",e);
			try { c.rollback(); } catch (Exception e2) { fail(e2);}
		} finally {
			try { c.free(); } catch (Exception e) {}
		}
		return res;
	}

}
